﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using LFSRecord2.Misc;
using LFSRecord2.Model;
using LFSRecord2.Model.UserActions;
using System.Windows.Media.Media3D;
using LFSRecord2.Events;

namespace LFSRecord2.View.TimelineStructures
{
    /// <summary>
    /// Interaction logic for DoubleValueEditControl.xaml
    /// </summary>
    public partial class DoubleValueEditControl : Grid, INotifyPropertyChanged
    {
        public bool IsEditable { get; set; }
        public string ValueName { get; set; }
        public bool CanSlow = true;
        public double ValueStep = 0.1;
        public double ValuePrecision = 0.01;
        public double ValueScaleFactor = 1.0d;
        public double FontSize = 10;
        public ValueEditControlDisplayTypes DisplayType = ValueEditControlDisplayTypes.Plain;

        public static DependencyProperty ValueProperty = DependencyProperty.Register(
              "Value",
              typeof(double),
              typeof(DoubleValueEditControl),
              new FrameworkPropertyMetadata(new PropertyChangedCallback(handleValueChange))
            );
        public double Value
        {
            get
            {
                return Math.Round((double)GetValue(ValueProperty) / (ValuePrecision / ValueScaleFactor)) * (ValuePrecision / ValueScaleFactor);
            }
            set
            {
                SetValue(ValueProperty, value);
                OnPropertyChanged(ValueName);
            }
        }

        public static DependencyProperty HasBoundPropertyProperty = DependencyProperty.Register(
              "HasBoundProperty",
              typeof(bool),
              typeof(DoubleValueEditControl),
              new FrameworkPropertyMetadata(new PropertyChangedCallback(handleHasBoundPropertyPropertyChange))
            );
        public bool HasBoundProperty
        {
            get
            {
                return (bool)GetValue(HasBoundPropertyProperty);
            }
            set
            {
                SetValue(HasBoundPropertyProperty, value);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }


        public DoubleValueEditControl()
        {
            IsEditable = true;
            InitializeComponent();
        }

        static void handleValueChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((DoubleValueEditControl)d).writeLabelValue();
        }

        static void handleHasBoundPropertyPropertyChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Debug.WriteLine("handling valuebinding property change");
            ((DoubleValueEditControl)d).writeLabelValue();
        }

        public void writeLabelValue()
        {
            // Set colors (according to HasBoundProperty
            if (!IsEditable || HasBoundProperty)
            {
                ValueLabel.Foreground = new SolidColorBrush(Colors.DarkGray);
                ValueUnderline.Visibility = Visibility.Hidden;
            }
            else
            {
                ValueLabel.Foreground = new SolidColorBrush(Color.FromArgb(255, 237, 170, 5));
                ValueUnderline.Visibility = Visibility.Visible;
            }

            // Write label
            double v = Value * ValueScaleFactor;
            ValueLabel.FontSize = FontSize;
            if (DisplayType == ValueEditControlDisplayTypes.Timestamp)
            {
                ValueLabel.Text = TimeHelper.Seconds2TimeStamp(v);
            }
            else if (DisplayType == ValueEditControlDisplayTypes.FrameTimestamp)
            {
                ValueLabel.Text = TimeHelper.FrameNumToPlayHeadText((int)v, LfsRecordController.project.FPS);
            }
            else
            {
                ValueLabel.Text = v.ToString("#0.00", CultureInfo.InvariantCulture);
            }
            //ValueLabel.Text = (Value * ValueScaleFactor).ToString("#0.00", CultureInfo.InvariantCulture);
        }

        Point _startPoint;
        double _origValue;
        bool _hasMoved;
        private void ValueLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            if (!IsEditable || HasBoundProperty)
                return;

            _startPoint = e.GetPosition(this);
            _origValue = Value;
            _hasMoved = false;
            Application.Current.MainWindow.CaptureMouse();
            Application.Current.MainWindow.MouseUp += ValueLabel_MouseUp;
            Application.Current.MainWindow.MouseMove += ValueLabel_MouseMove;

            //Mouse.OverrideCursor = Cursors.None;
        }

        private void ValueLabel_MouseUp(object sender, MouseButtonEventArgs e)
        {
            //Debug.WriteLine("and up");

            //Mouse.OverrideCursor = null;

            Application.Current.MainWindow.ReleaseMouseCapture();
            Application.Current.MainWindow.MouseUp -= ValueLabel_MouseUp;
            Application.Current.MainWindow.MouseMove -= ValueLabel_MouseMove;

            if (_origValue != Value)
            {
                // Record user action
                OnDoubleValueChanged(new DoubleValueChangedEventArgs(_origValue, Value));
            }

            if (!_hasMoved)
            {
                // Show input box for keyboard input
                ShowInputBox();
            }
        }

        private void ShowInputBox()
        {
            //Debug.WriteLine("Showing input box");
            InputBox.FontSize = FontSize;
            if (DisplayType == ValueEditControlDisplayTypes.FrameTimestamp)
            {
                InputBox.FontSize = FontSize - 1;
                InputBox.Text = TimeHelper.FrameNumToPlayHeadText((int)(Value * ValueScaleFactor), LfsRecordController.project.FPS);
                InputBox.Width = FontSize * 4;
            }
            else
            {
                InputBox.Text = (Value * ValueScaleFactor).ToString(CultureInfo.InvariantCulture);
            }
            InputBox.Visibility = Visibility.Visible;
            InputBox.SelectAll();
            InputBox.Focus();

            _newInput = (string)ValueLabel.Text;
            _caretIndex = _newInput.Length;

            InputBox.TextChanged += InputBox_TextChanged;
            InputBox.KeyDown += InputBox_KeyDown;
            InputBox.SelectionChanged += InputBox_SelectionChanged;
            Application.Current.MainWindow.PreviewMouseDown += Global_MouseDown;
        }

        void InputBox_SelectionChanged(object sender, RoutedEventArgs e)
        {
            _caretIndex = InputBox.CaretIndex;
        }

        private void ValueLabel_MouseMove(object sender, MouseEventArgs e)
        {
            Point curPoint = e.GetPosition(this);
            Point diff = new Point(curPoint.X - _startPoint.X, _startPoint.Y - curPoint.Y);
            _startPoint = curPoint;

            if (diff.X + diff.Y == 0)
                return;

            _hasMoved = true;

            double valueMult;
            if (CanSlow)
            {
                valueMult = (Keyboard.IsKeyDown(Key.LeftCtrl)) ?
                                    1 :
                                    ((Keyboard.IsKeyDown(Key.LeftShift)) ? 0.1 : 10);
            }
            else
            {
                valueMult = 10;
            }
            Value = Value + (diff.Y + diff.X) * ((ValueStep * valueMult) / ValueScaleFactor);
            writeLabelValue();
        }

        string _newInput = "";
        int _caretIndex = 0;
        bool _ignoreTextUpdate = false;
        private void InputBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            // Validate the text
            double result = 0;
            //Debug.WriteLine("Caret index : {0}", InputBox.CaretIndex);

            if (DisplayType == ValueEditControlDisplayTypes.FrameTimestamp)
            {
                //Debug.WriteLine("Would log the following line : " + InputBox.Text);

                //Logger.LogDebug(InputBox.Text, true);

                if (InputBox.Text.Length > 0 &&
                    InputBox.Text != "." &&
                    InputBox.Text != ":" &&
                    !TimeHelper.PlayHeadTextToFrameNum(InputBox.Text, LfsRecordController.project.FPS, ref result))
                {
                    _ignoreTextUpdate = true;
                    InputBox.Text = _newInput;
                    _ignoreTextUpdate = false;
                    InputBox.CaretIndex = _caretIndex;
                }
                else
                {
                    _newInput = InputBox.Text;
                    if (!_ignoreTextUpdate)
                        _caretIndex = InputBox.CaretIndex;
                }
            }
            else
            {
                if (InputBox.Text.Length > 0 &&
                    InputBox.Text != "." &&
                    InputBox.Text != "-" &&
                    !double.TryParse(InputBox.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out result))
                {
                    _ignoreTextUpdate = true;
                    InputBox.Text = _newInput;
                    _ignoreTextUpdate = false;
                    InputBox.CaretIndex = _caretIndex;
                }
                else
                {
                    _newInput = InputBox.Text;
                    if (!_ignoreTextUpdate)
                        _caretIndex = InputBox.CaretIndex;
                }
            }
        }

        private void InputBox_KeyDown(object sender, KeyEventArgs e)
        {
            //Debug.WriteLine("kb input : " + e.Key);

            double result = 0;
            if (e.Key == Key.Enter)
            {
                e.Handled = true;
                if (DisplayType == ValueEditControlDisplayTypes.FrameTimestamp)
                {
                    if (!TimeHelper.PlayHeadTextToFrameNum(InputBox.Text, LfsRecordController.project.FPS, ref result))
                        return;
                    result = Math.Round(result);
                }
                else
                {
                    if (!double.TryParse(InputBox.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out result))
                        return;
                }


                if (Value != result / ValueScaleFactor)
                {
                    Value = result / ValueScaleFactor;
                }

                if (_origValue != Value)
                {
                    // Record user action
                    OnDoubleValueChanged(new DoubleValueChangedEventArgs(_origValue, Value));
                }
                HideInputBox();
            }
            else if (e.Key == Key.Tab)
            {
                e.Handled = true;

                StackPanel parent = (StackPanel)Parent;
                if (parent.Children.Count > 1)
                {
                    if (DisplayType == ValueEditControlDisplayTypes.FrameTimestamp)
                    {
                        if (TimeHelper.PlayHeadTextToFrameNum(InputBox.Text, LfsRecordController.project.FPS, ref result))
                        {
                            result = Math.Round(result);
                            if (Value != result / ValueScaleFactor)
                            {
                                Value = result / ValueScaleFactor;

                                if (_origValue != Value)
                                {
                                    // Record user action
                                    OnDoubleValueChanged(new DoubleValueChangedEventArgs(_origValue, Value));
                                }
                            }
                        }
                    }
                    else
                    {
                        if (double.TryParse(InputBox.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out result))
                        {
                            if (Value != result / ValueScaleFactor)
                            {
                                Value = result / ValueScaleFactor;

                                if (_origValue != Value)
                                {
                                    // Record user action
                                    OnDoubleValueChanged(new DoubleValueChangedEventArgs(_origValue, Value));
                                }
                            }
                        }
                    }
                    HideInputBox(false);

                    DoubleValueEditControl dvc = null;
                    int dvc1Index = -1;
                    bool found = false;
                    for (int a = 0; a < parent.Children.Count; a++)
                    {
                        if (parent.Children[a].GetType() == typeof(DoubleValueEditControl))
                        {
                            dvc = (DoubleValueEditControl)parent.Children[a];
                            if (dvc1Index < 0)
                                dvc1Index = a;

                            if (found)
                            {
                                dvc.ShowInputBox();
                                return;
                            }
                            else if (dvc == this)
                            {
                                found = true;
                            }
                        }
                    }

                    if (dvc1Index  > -1)
                        ((DoubleValueEditControl)parent.Children[dvc1Index]).ShowInputBox();
                }
            }
            else if (e.Key == Key.Escape)
            {
                e.Handled = true;
                HideInputBox();
            }
        }

        private void HideInputBox(bool focusTurnAway = true)
        {
            Application.Current.MainWindow.PreviewMouseDown -= Global_MouseDown;
            InputBox.SelectionChanged -= InputBox_SelectionChanged;
            InputBox.TextChanged -= InputBox_TextChanged;
            InputBox.KeyDown -= InputBox_KeyDown;
            InputBox.Visibility = Visibility.Collapsed;
            _newInput = "";
            if (focusTurnAway)
                Application.Current.MainWindow.Focus();
        }

        private void Global_MouseDown(object sender, MouseButtonEventArgs e)
        {
            //Debug.WriteLine("Sender type : " + e.OriginalSource.GetType());
            double result = 0;
            if (e.OriginalSource.GetType().ToString() != "System.Windows.Controls.TextBoxView")
            {
                if (DisplayType == ValueEditControlDisplayTypes.FrameTimestamp)
                {
                    if (TimeHelper.PlayHeadTextToFrameNum(InputBox.Text, LfsRecordController.project.FPS, ref result))
                    {
                        result = Math.Round(result);
                        if (Value != result / ValueScaleFactor)
                        {
                            Value = result / ValueScaleFactor;

                            if (_origValue != Value)
                            {
                                // Record user action
                                OnDoubleValueChanged(new DoubleValueChangedEventArgs(_origValue, Value));
                            }
                        }
                    }
                }
                else
                {
                    if (double.TryParse(InputBox.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out result))
                    {
                        if (Value != result)
                        {
                            if (Value != result / ValueScaleFactor)
                            {
                                Value = result / ValueScaleFactor;

                                if (_origValue != Value)
                                {
                                    // Record user action
                                    OnDoubleValueChanged(new DoubleValueChangedEventArgs(_origValue, Value));
                                }
                            }
                        }
                    }
                }
                HideInputBox();
            }
        }

        public event EventHandler<DoubleValueChangedEventArgs> DoubleValueChanged;

        public virtual void OnDoubleValueChanged(DoubleValueChangedEventArgs e)
        {
            EventHandler<DoubleValueChangedEventArgs> temp = DoubleValueChanged;
            if (temp != null)
            {
                temp(this, e);
            }
        }
    }
}
